/* Copyright (C) 2011 Riccardo Marson

   This file is part of OpenPool, an open-source game of pool. 

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICCARDO MARSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
   AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  See Readme.rtf for further information.
  
*/

#include "Constants.h"

#include "glengine.h"
#include "rescache.h"

#include <iostream>
#include <fstream>
#include <sstream>

CRenderWindow::CGraphicsListener::CGraphicsListener(string name, CRenderWindow *parent) :
    CBaseEventListener(name)
{
    mParent = parent;
}

CRenderWindow::CGraphicsListener::~CGraphicsListener()
{

}

bool CRenderWindow::CGraphicsListener::processEvent(EventPtr event)
{
    eEventType evT = event->getEventType();

    switch (evT)
    {
    case EV_BALL_MOVED: {
        CBallMovedEvent * m_event = dynamic_cast<CBallMovedEvent*>(event.get());
        mParent->updateAvatar(m_event->id(), m_event->position(), m_event->heading(),
                              m_event->rotationAxis(), m_event->rotationAngle());
        break;
    }
    case EV_CUEBALL_POCKETED: {
        CCueBallPocketedEvent * m_event = dynamic_cast<CCueBallPocketedEvent*>(event.get());
        mParent->updateAvatar(OBJ_CUE_BALL, m_event->position(), CVector3(0.0, 0.0, 1.0),
                              CVector3(1.0, 0.0, 0.0), 0.0);
        ostringstream ss;
        ss << m_event->score();
        mParent->currentScore = ss.str();
        break;
    }
    case EV_BALL_POCKETED: {
        CBallPocketedEvent * m_event = dynamic_cast<CBallPocketedEvent*>(event.get());
        ostringstream ss1, ss2;
        ss1 << m_event->score();
        mParent->currentScore = ss1.str();
        ss2 << m_event->ballsLeft();
        mParent->activeBalls = ss2.str();
        break;
    }
    case EV_GAME_ENDED: {
        CGameEndedEvent * m_event = dynamic_cast<CGameEndedEvent*>(event.get());
        mParent->endGame = true;
        mParent->gameIsWon = m_event->gameIsWon();
        break;
    }
    case EV_START_GAME_REQUESTED: {
        mParent->endGame = false;
        mParent->gameIsWon = false;
        mParent->activeBalls = "15";
        mParent->currentScore = "0";
        break;
    }
    //all other events are not handled. the default case should never be called, as each
    //listener is already registered only to events of interest. However, it is useful as
    //a precaution and to avoid compile-time warnings.
    default:
        return false;
    }

    return true;
}

//***************************************************************
//Window-related
//***************************************************************

CRenderWindow::CRenderWindow(string listenerName) :
       mEventListener( new CGraphicsListener(listenerName, this) )
{
    CLogManager::getSingleton()->logMessage("Initializing OpenGL Graphics Engine...");

    castShadows = true;
    shootingMode = false;
    physicsDebugModeOn = false;

    // intitial dimensions
    mWidth = 1024;
    mHeight = 768;

    //Init window and OpenGL context
    initializeGL();

    currentScore = "0";
    activeBalls = "15";
    endGame = false;
    gameIsWon = false;

    //register the listener in the Event Manager
    CEventManager::getSingleton()->registerListener(EV_START_GAME_REQUESTED, mEventListener);
    CEventManager::getSingleton()->registerListener(EV_BALL_MOVED, mEventListener);
    CEventManager::getSingleton()->registerListener(EV_CUEBALL_POCKETED, mEventListener);
    CEventManager::getSingleton()->registerListener(EV_BALL_POCKETED, mEventListener);
    CEventManager::getSingleton()->registerListener(EV_GAME_ENDED, mEventListener);
}

CRenderWindow::~CRenderWindow()
{
    CLogManager::getSingleton()->logMessage("OpengGL Engine Shutting Down.");

    mLights.clear();

    //Note: shared_ptr remembers the original class along with the correct decostructor, even if the
    //template is declared as a parent class.
    mBalls.clear();
    mProps.clear();

    //Delete FBOs
    glDeleteFramebuffersEXT(2, mFBO);

    //unload all textures
    for (TexturesMap::iterator it = mTextures.begin(); it != mTextures.end(); ++it)
    {
    	if ( ((*it).first != "shadow") && ((*it).first != "reflection") )
    		CResourceCache::getSingletonPtr()->unloadResource((*it).first);
    	else
    		glDeleteTextures(1, &(*it).second);

    	(*it).second = 0;
    }

    mTextures.clear();

    //unload all models
    for (ModelsMap::iterator it = mModels.begin(); it != mModels.end(); ++it)
    {
    	CResourceCache::getSingletonPtr()->unloadResource((*it).first);
    	(*it).second = NULL;
    }

    mModels.clear();

    mWindow->Close();

    delete mWindow;

    CEventManager::getSingleton()->unregisterListener(mEventListener);
}

int CRenderWindow::width()
{
    return mWidth;
}

int CRenderWindow::height()
{
    return mHeight;
}

void CRenderWindow::resize(unsigned int w, unsigned int h)
{
    mWindow->SetSize(w, h);
}

void CRenderWindow::pollUIEvents()
{
    sf::Event uiEvent;

    // retrieve all queued events.

    // Note: it might be necessary to time-limit this operation
    // and leave unprocessed events for the next loop.

    while (mWindow->GetEvent(uiEvent))
    {
        if (uiEvent.Type == sf::Event::Closed)
        {
            EventPtr c_event( new CBaseEvent( EV_QUIT_APPLICATION ) );
            CEventManager::getSingleton()->queueEvent(c_event);
        }

        if (uiEvent.Type == sf::Event::Resized)
            resizeGL(uiEvent.Size.Width, uiEvent.Size.Height);

        if (uiEvent.Type == sf::Event::MouseMoved)
            mouseMoveEvent(uiEvent.MouseMove.X, uiEvent.MouseMove.Y);

        if (uiEvent.Type == sf::Event::MouseButtonPressed)
            mousePressEvent(uiEvent.MouseButton.Button, uiEvent.MouseButton.X, uiEvent.MouseButton.Y);

        if (uiEvent.Type == sf::Event::MouseWheelMoved)
            mouseWheelEvent(uiEvent.MouseWheel.Delta);

        if (uiEvent.Type == sf::Event::KeyPressed)
            keyPressEvent(uiEvent.Key.Code);
    }

}

void CRenderWindow::updateGraphics()
{
    mWindow->SetActive();
    mWindow->Clear();

    renderScene();

    mWindow->Display();
}

void CRenderWindow::renderScene()
{
    if (castShadows)
        shadowRender();
    else
        standardRender();


    //Prepare to Paint 2D Overlay.
    glShadeModel(GL_FLAT);
    glDisable(GL_CULL_FACE); //vital for displaying correctly the overlay.
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glDisable(GL_BLEND); //added
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    //****END MAIN SCENE

    // Draw Overlay

    glPushMatrix();

    glViewport(0, 0, mWidth, mHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, mWidth, mHeight, 0, -1, 1);

    glPushMatrix();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    string text_topleft;

    if (shootingMode)
        text_topleft = "Mouse Mode: Shooting";
    else
        text_topleft = "Mouse Mode: Selection";

    string text_bottomleft;
    text_bottomleft = "Score: ";
    text_bottomleft += currentScore;

    string text_bottomright;
    text_bottomright = "Balls Left: ";
    text_bottomright += activeBalls;

    stringstream ss(ios::in | ios::out);
    ss << ( 1.0 / mWindow->GetFrameTime() );

    string text_topright;
        text_topright = "FPS: ";
        text_topright += ss.str();

    ss.clear();

    sf::String topLeft( sf::Unicode::Text(text_topleft), sf::Font::GetDefaultFont(), 25 );
    sf::String bottomLeft( sf::Unicode::Text(text_bottomleft), sf::Font::GetDefaultFont(), 25  );
    sf::String bottomRight( sf::Unicode::Text(text_bottomright), sf::Font::GetDefaultFont(), 25  );
    sf::String topRight( sf::Unicode::Text(text_topright), sf::Font::GetDefaultFont(), 20  );


    topLeft.SetPosition(20, 20);
    topLeft.SetColor(sf::Color::White);

    bottomLeft.SetPosition(20, mHeight - 40);
    bottomLeft.SetColor(sf::Color::Yellow);

    bottomRight.SetPosition(mWidth - 200, mHeight - 40);
    bottomRight.SetColor(sf::Color::Magenta);

    topRight.SetPosition(mWidth - 200, 20);
    topRight.SetColor(sf::Color::Red);


    mWindow->Draw(topLeft);

    mWindow->Draw(bottomLeft);

    mWindow->Draw(bottomRight);

    mWindow->Draw(topRight);

    if (endGame)
    {
        string end_text;

        if (gameIsWon)
            end_text = "You Win!";
        else
            end_text = "You Loose!";

        sf::String end( sf::Unicode::Text(text_topleft), sf::Font::GetDefaultFont(), 32 );

        end.SetPosition(mWidth/2 - 100, mHeight/2 - 40);

        glColor3f(200.0, 50.0, 60.0);

        mWindow->Draw(end);
    }

    glPopMatrix();
    glPopMatrix();

    //*****END OVERLAY
}

//--------------------------------------------------------
//Widget Behaviour
//--------------------------------------------------------

void CRenderWindow::resizeGL(int width, int height)
{
    mWidth = width;
    mHeight = height;

    glViewport(0, 0, width, height);

    //Reset the dimensions of the pick buffer too, to match those of the new window!
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mRenderBuffer[1]);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGB, width, height);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    double aspectRatio = width / (double)height;

    gluPerspective(45.0f, aspectRatio, mCamera->GetClipDistanceNear(),
                   mCamera->GetClipDistanceFar());

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    updateGraphics();

}

//--------------------------------------------------------
//Human Interaction: Mouse
//--------------------------------------------------------


void CRenderWindow::mousePressEvent(sf::Mouse::Button button, int x, int y)
{
    lastPos.x = x;
    lastPos.y = y;

    if (button == sf::Mouse::Left)
    {
        if (!shootingMode)
            selectionRender(x, y);
        else
        {
            PoolBalls::iterator it = mBalls.find(OBJ_CUE_BALL);

            CVector3 vel = cueDirectionAtHit * cueVelocityAfterHit;
            EventPtr c_event( new CCueBallLaunchedEvent( (*it).second->Position(), vel) );
            CEventManager::getSingleton()->sendEvent(c_event);

            mWindow->ShowMouseCursor(true);
            shootingMode = false;
        }
    }

}

void CRenderWindow::mouseMoveEvent(int x, int y)
{
    int dx = x - lastPos.x;
    int dy = y - lastPos.y;

    lastPos.x = x;
    lastPos.y = y;

    float rotFactor = 20.0;
    float moveFactor = 20.0;

    static bool mousePosReset = false;

    const sf::Input &userInput = mWindow->GetInput();

    if (shootingMode && !mousePosReset)
    {
        SceneProps::iterator it;

        it = mProps.find("cue");

        if ( it != mProps.end() )
        {
            float rotAngRad = (*it).second->RotationAngle() * mathc::DEG2RAD;
            float deltaRot = 0.02, deltaTras = 0.12, maxTras = 2.0, minTras = deltaTras;
            CVector3 dir;

            //choose one of two rotation modes for the cue based on the intensity of the mouse horizontal movement:
            //if dx is small use precision mode, else allow the cue to rotate with a bigger angle
            int threshold = 20;

            if (fabs(dx) <  threshold)
                deltaRot = 0.02;
            else if (fabs(dx) >=  threshold)
                deltaRot = 0.15;

            if (fabs(dy) < threshold)
                deltaTras = 0.02;
            else if (fabs(dy) >=  threshold)
                deltaTras = 0.12;

            if (dx > 0)
            {
                rotAngRad += deltaRot;
                dir = CVector3::RotateY( +deltaRot, (*it).second->Heading() );
                //dir = (*it).second->Heading().rotateY( +deltaRot );
            }
            else if (dx < 0)
            {
                rotAngRad -= deltaRot;
                dir = CVector3::RotateY( -deltaRot, (*it).second->Heading() );
                //dir = (*it).second->Heading().rotateY( -deltaRot );
            }
            else
                dir = (*it).second->Heading();

            (*it).second->RotationAngle() = rotAngRad * mathc::RAD2DEG;
            (*it).second->Heading() = dir;

            if (dy > 0)
                (*it).second->Offset() -= deltaTras;
            else if (dy < 0)
                (*it).second->Offset() += deltaTras;

            if ((*it).second->Offset() > maxTras)
                (*it).second->Offset() = maxTras;

            if ((*it).second->Offset() < minTras)
                (*it).second->Offset() = minTras;

            (*it).second->Position() = (*mBalls.find(OBJ_CUE_BALL)).second->Position() - dir*(*it).second->Offset();
            cueVelocityAfterHit = (*it).second->Offset() * 8.0;
            cueDirectionAtHit = (*it).second->Heading();

            // update only when an update is called from the main loop.
            // updateGraphics();
        }

        //reset mouse position
        mousePosReset = true;
        mWindow->SetCursorPosition(mousePivot.x, mousePivot.y);

    }
    else  if (shootingMode && mousePosReset)
        mousePosReset = false;

    if ( userInput.IsMouseButtonDown( sf::Mouse::Right ) )
    {
        if ( userInput.IsKeyDown( sf::Key::LControl ) )
            moveCamera(dx / moveFactor);
        else
            rotateCamera((dx / rotFactor),(dy / rotFactor));

        // update only when an update is called from the main loop.
        // updateGraphics();
    }

}

void CRenderWindow::mouseWheelEvent(int delta)
{
    float moveFactor = 50.0;

    moveCamera(delta / moveFactor);
}

//--------------------------------------------------------
//Human Interaction: Keyboard
//--------------------------------------------------------

void CRenderWindow::keyPressEvent(sf::Key::Code key)
{
    float dx = 2.0;

    EventPtr k_event;

    switch (key)
    {
    case sf::Key::Escape:
        k_event = EventPtr(new CKeyEvent(key));
        if (k_event != NULL)
            CEventManager::getSingleton()->queueEvent(k_event);
        break;
    //Create a new ball
    case sf::Key::Space:
        break;
    //Camera movement. W,S,A,D has same effect of arrow keys
    case sf::Key::W:
        rotateCamera(0.0, dx);
        break;
    case sf::Key::S:
        rotateCamera(0.0, -dx);
        break;
    case sf::Key::A:
        rotateCamera(-dx, 0.0);
        break;
    case sf::Key::D:
        rotateCamera(dx, 0.0);
        break;
    case sf::Key::Up:
        rotateCamera(0.0, dx);
        break;
    case sf::Key::Down:
        rotateCamera(0.0, -dx);
        break;
    case sf::Key::Left:
        rotateCamera(-dx, 0.0);
        break;
    case sf::Key::Right:
        rotateCamera(dx, 0.0);
        break;
    case sf::Key::PageUp:
        moveCamera(dx);
        break;
    case sf::Key::PageDown:
        moveCamera(-dx);
        break;
    case sf::Key::P:
    	saveScreenshot();
    	break;
    default:
        break;
    }

}

//****************************************************************
// Utilities
//****************************************************************

void CRenderWindow::clearScene()
{
    mBalls.clear();
}

CVector3 CRenderWindow::getCurrentCameraPosition()
{
    return mCamera->GetPosition();
}

CVector3 CRenderWindow::getCurrentCameraDirection()
{
    return mCamera->GetLookAt();
}

void CRenderWindow::toggleShadows(bool shadowOn)
{
    castShadows = shadowOn;
}

void CRenderWindow::setDebugModeActive(bool physicsDebugMode)
{
    physicsDebugModeOn = physicsDebugMode;
}

//A simple implementation of a first person camera.

void CRenderWindow::moveCamera(float dx)
{
    //Zoom in/out w.r.t. the pool table

    //get the current camera position and look point (world coordinates)
    CVector3 camPos = mCamera->GetPosition();
    //temp vectors
    CVector3 camDir, newPosition, newLookAt(0.0, 5.0, 0.0);
    float R;

    R = camPos.GetLength();
    camDir = camPos * (1.0 / R);

    R += dx;

    if (R > 20.0)
        R = 20.0;

    if (R < 6.0)
        R = 6.0;

    newPosition = camDir * R;

    //update camera
    mCamera->SetPosition(newPosition);
    mCamera->SetLookAt(newLookAt);

    EventPtr cm_event( new CCameraMovedEvent( newPosition ) );
    CEventManager::getSingleton()->queueEvent(cm_event);

    // update only when an update is called from the main loop.
    //this->update();

}

void CRenderWindow::rotateCamera(float dx, float dy)
{
    //rotate in a sphere of fixed radius around [0,0,0] always looking towards [0,0,0]
    float R = mCamera->GetPosition().GetLength();
    static float anglex = 3.14, angley = 0.52;

    CVector3 newPosition;
    CVector3 lookPoint(0.0, 3.10, 0.0);

    anglex += dx*mathc::DEG2RAD;
    angley += dy*mathc::DEG2RAD;

    if (angley < 0.17)
        angley = 0.17;

    if (angley > 2.96)
        angley = 2.96;

    newPosition.SetComponents(  R * sin(anglex) * cos(angley),
                                R * sin(angley),
                                R * cos(anglex) * cos(angley) );

    mCamera->SetPosition(newPosition);
    mCamera->SetLookAt(lookPoint);

    EventPtr cm_event( new CCameraMovedEvent( newPosition ) );
    CEventManager::getSingleton()->queueEvent(cm_event);

}

void CRenderWindow::addObject(GLObjectPtr obj)
{
	if (obj->HasModel())
	{
		obj->SetModelPtr( loadObjModel(obj->ModelFileName().c_str()) );
		mModels[obj->ModelFileName()] = obj->ModelPtr();
	}

	switch (obj->Type())
	{
	case TYPE_PROP:
		mProps[obj->Name()] = obj;
		break;
	case TYPE_STATIC:
		mProps[obj->Name()] = obj;
		break;
	case TYPE_MOVABLE:
		mBalls[obj->Id()] = obj;
		break;
	default:
		break;
	}
}

void CRenderWindow::updateAvatar(eObjectID ballID, CVector3 newPosition, CVector3 newHeading,
                            CVector3 newRotAxis, float newRotAngle)
{
    //Change the coordinate system to OpenGL before updating
    CVector3 glPosition(-newPosition.x(), newPosition.z(), newPosition.y());
    CVector3 glHeading(-newHeading.x(), newHeading.z(), newHeading.y());
    CVector3 glRotAx(-newRotAxis.x(), newRotAxis.z(), newRotAxis.y());
    float rotAng = newRotAngle * mathc::RAD2DEG;

    PoolBalls::iterator b_it = mBalls.find(ballID);

    if (b_it != mBalls.end())
    {
        (*b_it).second->Position() = glPosition;
        (*b_it).second->Heading() = glHeading;
        (*b_it).second->RotationAxis() = glRotAx;
        (*b_it).second->RotationAngle() = rotAng;
    }

}

void CRenderWindow::initCamera(GLCameraPtr camera)
{
    mCamera = camera;
}

void CRenderWindow::addLight(GLLightPtr light)
{
    mLights.push_back(light);
}


//********************************************************************
// OpenGL Utilities
//********************************************************************

const ModelOBJ* CRenderWindow::loadObjModel(const char *objFilename)
{
    // Import the OBJ file and normalize to unit length.

	const ModelOBJ *model = NULL;

	ResHandlePtr objH = CResourceCache::getSingletonPtr()->requestResource(objFilename, RES_MODEL);

	if (objH->getType() == RES_MODEL)
	{
		CObjResHandle* obj = dynamic_cast<CObjResHandle*>(objH.get());

		model = obj->model();
	}

    //model.normalize();

    // Load any associated textures.
    // Note the path where the textures are assumed to be located.

    const ModelOBJ::Material *pMaterial = 0;
    GLuint textureId = 0;
    std::string::size_type offset = 0;
    std::string filename;

    for (int i = 0; i < model->getNumberOfMaterials(); ++i)
    {
        pMaterial = &model->getMaterial(i);

        // Look for and load any diffuse color map textures.

        if (pMaterial->colorMapFilename.empty())
            continue;

        ResHandlePtr texColorH = CResourceCache::getSingletonPtr()->requestResource(pMaterial->colorMapFilename, RES_TEXTURE);

        if (texColorH->getType() == RES_TEXTURE)
        {
        	CImageResHandle* tex = dynamic_cast<CImageResHandle*>(texColorH.get());

        	textureId = tex->glTextureID;
        }

        if (textureId != 0)
            mTextures[pMaterial->colorMapFilename] = textureId;

        // Look for and load any normal map textures.

        if (pMaterial->bumpMapFilename.empty())
            continue;

        // Try load the texture using the path in the .MTL file.

        textureId = 0;

        ResHandlePtr texBumpH = CResourceCache::getSingletonPtr()->requestResource(pMaterial->bumpMapFilename, RES_TEXTURE);

        if (texBumpH->getType() == RES_TEXTURE)
        {
        	CImageResHandle* tex = dynamic_cast<CImageResHandle*>(texBumpH.get());

        	textureId = tex->glTextureID;
        }

        if (textureId != 0)
            mTextures[pMaterial->bumpMapFilename] = textureId;
    }

    return model;
}


//-------------------------------------------------------
//OpenGL Init
//-------------------------------------------------------

void CRenderWindow::initializeGL()
{
    // create render window
    sf::WindowSettings Settings;
    Settings.DepthBits         = 24; // Request a 24 bits depth buffer
    Settings.StencilBits       = 8;  // Request a 8 bits stencil buffer
    Settings.AntialiasingLevel = 2;  // Request 2 levels of antialiasing
    
    mWindow = new sf::RenderWindow(sf::VideoMode(mWidth, mHeight, 32), "OpenPool",  sf::Style::Close, Settings);


    //Load OpenGL extensions (allows for esier portability)
    GLenum gl_err = glewInit();
    string glMsg;

    if (gl_err != GLEW_OK)
    {
        glMsg = "OpenGL Init Error! ";
        glMsg += (char*)glewGetErrorString(gl_err);

        CLogManager::getSingleton()->logMessage(glMsg.c_str());
    }

    glMsg = "OpenGL Version: ";
    glMsg += (char*)glGetString(GL_VERSION);
    CLogManager::getSingleton()->logMessage(glMsg.c_str());

    glMsg = "GPU: ";
    glMsg += (char*)glGetString(GL_VENDOR);
    glMsg += " ";
    glMsg += (char*)glGetString(GL_RENDERER);
    CLogManager::getSingleton()->logMessage(glMsg.c_str());

    glMsg = "GLSL Version: ";
    glMsg += (char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
    CLogManager::getSingleton()->logMessage(glMsg.c_str());

    glMsg = "Using GLEW Version: ";
    glMsg += (char*)glewGetString(GLEW_VERSION);
    CLogManager::getSingleton()->logMessage(glMsg.c_str());

    if (CLogManager::getSingleton()->isDebugModeActive())
    {
        glMsg = "OpenGL Extensions Supported: \n\t\t\t";
        string glMsg_tmp = (char*)glGetString(GL_EXTENSIONS);

        size_t start = 0;
        size_t stop = glMsg_tmp.find(" ", start);
        while (stop != string::npos)
        {
            glMsg_tmp.replace(stop, 1, "\n\t\t\t");
            start = stop + 1;
            stop = glMsg_tmp.find(" ", start);
        }

        glMsg += glMsg_tmp;
        CLogManager::getSingleton()->logMessage(glMsg.c_str());
    }

    mWindow->PreserveOpenGLStates(true);

    //initialize the bias matrix used when computing the eye planes for projected texture mapping.
    //used in both shadow mapping and reflection mapping.
    biasMatrix(0,0) = 0.5; biasMatrix(0,1) = 0.0; biasMatrix(0,2) = 0.0; biasMatrix(0,3) = 0.0;
    biasMatrix(1,0) = 0.0; biasMatrix(1,1) = 0.5; biasMatrix(1,2) = 0.0; biasMatrix(1,3) = 0.0;
    biasMatrix(2,0) = 0.0; biasMatrix(2,1) = 0.0; biasMatrix(2,2) = 0.5; biasMatrix(2,3) = 0.0;
    biasMatrix(3,0) = 0.5; biasMatrix(3,1) = 0.5; biasMatrix(3,2) = 0.5; biasMatrix(3,3) = 1.0;

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    //Clear Buffers
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //some openGL options
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    //basic shadowmap texture dimensions, based on default window dimensions
    shadowMapWidth = 1024;
    shadowMapHeight = 1024;

    reflectionMapWidth = 2048;
    reflectionMapHeight = 2048;

    //determine if FBOs are supported by the hardware in use, else fall back
    if (GLEW_EXT_framebuffer_object)
    {
        useFBO = true;
        CLogManager::getSingleton()->logMessage("OpenGL Using Frame Buffer Objects.");

        //if FBOs are used, the shadow map can have arbitrary dimensions not bound to the screen buffer.
        //hence, a larger more detailed texture can be used
        shadowMapWidth *= 2;
        shadowMapHeight *= 2;
    }
    else
    {
        useFBO = false;
        CLogManager::getSingleton()->logMessage("EXT_framebuffer_object not supported, falling back to stadard mode.");
    }

    //setup shadow map texture
    GLuint shadowMap, reflectionMap;

    glGenTextures(1, &shadowMap);
    glBindTexture(GL_TEXTURE_2D, shadowMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0,
                 GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);

    glBindTexture(GL_TEXTURE_2D, 0);

    //setup ground reflection texture
    glGenTextures(1, &reflectionMap);
    glBindTexture(GL_TEXTURE_2D, reflectionMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, reflectionMapWidth, reflectionMapHeight, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D, 0);

    mTextures[string("shadow")] = shadowMap;
    mTextures[string("reflection")] = reflectionMap;

    if (useFBO)
    {
        //FBO generation
        glGenFramebuffersEXT(3, mFBO);

        //setup render buffer states associated with the depth texture buffer.
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO[0]);
        glGenRenderbuffersEXT(1, &mRenderBuffer[0]);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mRenderBuffer[0]);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, mRenderBuffer[0]);
        glDrawBuffer(GL_NONE);
        glReadBuffer(GL_NONE);
        GLenum fboStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
        if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
            CLogManager::getSingleton()->logMessage("FBO Error!");

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

        //setup render buffer states associated with the selection render buffer.
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO[1]);
        glGenRenderbuffersEXT(1, &mRenderBuffer[1]);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mRenderBuffer[1]);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGB, 1024, 768);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, mRenderBuffer[1]);
        fboStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
        if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
            CLogManager::getSingleton()->logMessage("FBO Error!");

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

        //setup render buffer states associated with the ground reflection texture.
        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, mFBO[2]);
        //render buffer for depth information (needed by GL_DEPTH_TEST)
        glGenRenderbuffersEXT(1, &mRenderBuffer[2]);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mRenderBuffer[2]);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, reflectionMapWidth, reflectionMapHeight);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, mRenderBuffer[2]);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
        //render the color buffer to texture
        glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                  GL_TEXTURE_2D, reflectionMap, 0);
        glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
        glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
        fboStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
        if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
            CLogManager::getSingleton()->logMessage("FBO Error!");

        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
    }

}


//--------------------------------------------------------
//GL Utility functions used in shadow mapping
//--------------------------------------------------------

void CRenderWindow::shadowMapProjection()
{

    /*projects the shadow texture from "light" space to "camera" space.
    The shadow map is generated by copying the depth buffer obtained
    by drawing the scene form the light point of view. It must be projected
    to the camera point of view in order to obtain the correct result.
    */

    CMatrix4x4 tmp;
    CMatrix4x4 shadowMatrix;

    tmp = lightView*lightProjection*biasMatrix;
    shadowMatrix = tmp.Transpose();

    TexturesMap::iterator iter = mTextures.find("shadow");

    glActiveTextureARB(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, iter->second);

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
    glEnable(GL_TEXTURE_GEN_Q);

    glTexGenfv(GL_S, GL_EYE_PLANE, shadowMatrix.GetRow(0));
    glTexGenfv(GL_T, GL_EYE_PLANE, shadowMatrix.GetRow(1));
    glTexGenfv(GL_R, GL_EYE_PLANE, shadowMatrix.GetRow(2));
    glTexGenfv(GL_Q, GL_EYE_PLANE, shadowMatrix.GetRow(3));

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

    glActiveTextureARB(GL_TEXTURE1);
    glDisable(GL_TEXTURE_2D);

}

void CRenderWindow::updateWorldMatrices()
{
    /*When is shadow-mapping mode, at each update save the current camera and light
      view and projection matrix. These are later used to generate the shadow map, calculate
      it projection to camera space and to update the observer position and look direction
      in place of calling gluLookAt().
     */
    GLLightPtr light = (*mLights.begin());
    float* lightpos = light->GetPosition();

    float lightRadius = sqrt(lightpos[0]*lightpos[0] + lightpos[1]*lightpos[1] +
                             lightpos[2]*lightpos[2]);
    float boundingRadius = 20.0;
    float lightNearPlane = .1;
    float lightFarPlane = lightNearPlane + 2.0*boundingRadius;
    float lightFOV = (2.0 * atan(boundingRadius / lightRadius))*mathc::RAD2DEG;

    glPushMatrix();

    //save the current camera projection matrix.
    glLoadIdentity();
    gluPerspective(45.0f, (float)mWidth/(float)mHeight, mCamera->GetClipDistanceNear(), mCamera->GetClipDistanceFar());
    glGetFloatv(GL_MODELVIEW_MATRIX, cameraProjection.Data());

    glLoadIdentity();
    //save the current camera view matrix.
    gluLookAt(mCamera->GetPosition().x(), mCamera->GetPosition().y(), mCamera->GetPosition().z(),
              mCamera->GetLookAt().x(), mCamera->GetLookAt().y(), mCamera->GetLookAt().z(),
              mCamera->GetUpVector().x(), mCamera->GetUpVector().y(), mCamera->GetUpVector().z());

    glGetFloatv(GL_MODELVIEW_MATRIX, cameraView.Data());

    glLoadIdentity();
    //save the current light projection matrix
    gluPerspective(lightFOV, (float)shadowMapWidth/(float)shadowMapHeight, lightNearPlane, lightFarPlane);
    glGetFloatv(GL_MODELVIEW_MATRIX, lightProjection.Data());

    glLoadIdentity();
    //save the current light view matrix.
    gluLookAt(lightpos[0], lightpos[1], lightpos[2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, lightView.Data());

    glPopMatrix();
}

//--------------------------------------------------------
// Drawing Functions
//--------------------------------------------------------

void CRenderWindow::drawObjModelFixedPipeline(const ModelOBJ *model, bool texturesOn)
{
    //Code Modified from 3rd party source, original license information follow

    //modified by R.Marson

   /* Copyright (c) 2007 dhpoware. All Rights Reserved.
    *
    * Permission is hereby granted, free of charge, to any person obtaining a
    * copy of this software and associated documentation files (the "Software"),
    * to deal in the Software without restriction, including without limitation
    * the rights to use, copy, modify, merge, publish, distribute, sublicense,
    * and/or sell copies of the Software, and to permit persons to whom the
    * Software is furnished to do so, subject to the following conditions:
    *
    * The above copyright notice and this permission notice shall be included in
    * all copies or substantial portions of the Software.
    */

    const ModelOBJ::Mesh *pMesh = 0;
    const ModelOBJ::Material *pMaterial = 0;
    const ModelOBJ::Vertex *pVertices = 0;
    TexturesMap::const_iterator iter;

    for (int i = 0; i < model->getNumberOfMeshes(); ++i)
    {
        pMesh = &model->getMesh(i);
        pMaterial = pMesh->pMaterial;
        pVertices = model->getVertexBuffer();

        if (pMaterial->name == "slate:1")
        {
            //pool table slate is the only surface that receives shadows
            //check must be done per surface and not per object

            if (castShadows)
            {
                TexturesMap::iterator ts_it = mTextures.find("shadow");

                glActiveTextureARB(GL_TEXTURE1);
                glEnable(GL_TEXTURE_2D);
                glBindTexture(GL_TEXTURE_2D, (*ts_it).second);
            }
        }
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, pMaterial->ambient);
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, pMaterial->diffuse);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, pMaterial->specular);
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, pMaterial->shininess * 128.0f);

        iter = mTextures.find(pMaterial->colorMapFilename);

        if ( ( iter != mTextures.end() ) && texturesOn)
        {
            glActiveTextureARB(GL_TEXTURE0);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, iter->second);
        }
        else
        {
            glActiveTextureARB(GL_TEXTURE0);
            glDisable(GL_TEXTURE_2D);
        }

        if (model->hasPositions())
        {
            glEnableClientState(GL_VERTEX_ARRAY);
            glVertexPointer(3, GL_FLOAT, model->getVertexSize(),
                            model->getVertexBuffer()->position);
        }

        if (model->hasTextureCoords() && texturesOn)
        {
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            glTexCoordPointer(2, GL_FLOAT, model->getVertexSize(),
                              model->getVertexBuffer()->texCoord);
        }

        if (model->hasNormals())
        {
            glEnableClientState(GL_NORMAL_ARRAY);
            glNormalPointer(GL_FLOAT, model->getVertexSize(),
                            model->getVertexBuffer()->normal);
        }

        glDrawElements(GL_TRIANGLES, pMesh->triangleCount * 3, GL_UNSIGNED_INT,
                       model->getIndexBuffer() + pMesh->startIndex);

        if (model->hasNormals())
            glDisableClientState(GL_NORMAL_ARRAY);

        if (model->hasTextureCoords() && texturesOn)
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);

        if (model->hasPositions())
            glDisableClientState(GL_VERTEX_ARRAY);

        if (pMaterial->name == "slate:1")
        {
            if (castShadows)
            {
                glActiveTextureARB(GL_TEXTURE1);
                glDisable(GL_TEXTURE_2D);
            }
        }

    }
}

void CRenderWindow::drawScene()
{
    //Draw all the objects present in the scene.
    if ( (mProps.empty()) ||  mBalls.empty() )
        return;

    SceneProps::iterator p_it;

    p_it = mProps.find("ground");

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glActiveTextureARB(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);

    //change the texture env mode to decal. since the reflection texture was generated in RGBA mode with a transparent background color,
    //decal allows the gorund material to show through where there is no reflection.
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    if (p_it != mProps.end())
    {
        //glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (*p_it).second->Material().diffuse);
        //glMaterialfv(GL_FRONT, GL_SPECULAR, (*p_it).second->Material().specular);
        //glMaterialfv(GL_FRONT, GL_SHININESS, &(*p_it).second->Material().shininess);

        TexturesMap::iterator t_it = mTextures.find("reflection");

        glBindTexture(GL_TEXTURE_2D, (*t_it).second);

        drawObjModelFixedPipeline((*p_it).second->ModelPtr(), true);
        //(*p_it).second->BasicDraw();
    }


    glActiveTextureARB(GL_TEXTURE1);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);

    //switch the tex env mode back to modulate for all other objects, so that the texture colors are mixed with the underlying material
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    p_it = mProps.find("table");

    if (p_it != mProps.end())
    {

        glPushMatrix();
        glTranslatef( (*p_it).second->Position().x(), (*p_it).second->Position().y(),
                     (*p_it).second->Position().z() );
        glRotatef( (*p_it).second->RotationAngle(), (*p_it).second->RotationAxis().x(),
                  (*p_it).second->RotationAxis().y(),
                   (*p_it).second->RotationAxis().z() );
        drawObjModelFixedPipeline( (*p_it).second->ModelPtr(), true );
        glPopMatrix();

    }

    glActiveTextureARB(GL_TEXTURE0);
    glDisable(GL_TEXTURE_2D);

    //first draw all the objects that do not receive a shadow
    if (shootingMode)
    {
        CVector3 shoot = (*mBalls.begin()).second->Position() + cueDirectionAtHit * (cueVelocityAfterHit/2.0);

        //draw a line representing the direction in which the cue ball will be shot and the
        //intensity of the shot.
        glEnable(GL_COLOR_MATERIAL);

        glLineWidth(2.0);
        glColor3ub(255,0,0);
        glBegin(GL_LINE_STRIP);
        glVertex3f( (*mBalls.find(OBJ_CUE_BALL)).second->Position().x(), (*mBalls.find(OBJ_CUE_BALL)).second->Position().y(), (*mBalls.find(OBJ_CUE_BALL)).second->Position().z() );
        glColor3ub(255, 0, 0);
        glVertex3f( shoot.x(), shoot.y(), shoot.z() );
        glEnd();

        glDisable(GL_COLOR_MATERIAL);

        //draw the cue prop
        p_it = mProps.find("cue");

        if (p_it != mProps.end())
        {
            glPushMatrix();
            glTranslatef( (*p_it).second->Position().x(), (*p_it).second->Position().y(),
                         (*p_it).second->Position().z() );
            glRotatef( (*p_it).second->RotationAngle(), (*p_it).second->RotationAxis().x(),
                      (*p_it).second->RotationAxis().y(),
                       (*p_it).second->RotationAxis().z() );
            drawObjModelFixedPipeline( (*p_it).second->ModelPtr(), true );
            glPopMatrix();
        }
    }

    //draw pool balls

    for (PoolBalls::iterator b_it = mBalls.begin(); b_it != mBalls.end(); ++b_it)
    {

        if ( ( (*b_it).second->HasModel() ) )
        {
            glPushMatrix();
            glTranslatef( (*b_it).second->Position().x(),
                         (*b_it).second->Position().y(),
                         (*b_it).second->Position().z() );
            glRotatef( (*b_it).second->RotationAngle(), (*b_it).second->RotationAxis().x(),
                      (*b_it).second->RotationAxis().y(), (*b_it).second->RotationAxis().z());
            drawObjModelFixedPipeline( (*b_it).second->ModelPtr(), true );
            glPopMatrix();
        }
        else
        {
            glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (*b_it).second->Material().diffuse);
            glMaterialfv(GL_FRONT, GL_SPECULAR, (*b_it).second->Material().specular);
            glMaterialfv(GL_FRONT, GL_SHININESS, &(*b_it).second->Material().shininess);

            (*b_it).second->BasicDraw();
        }
    }

    //if physics debug mode is active, collision shapes are also drawn
    if (physicsDebugModeOn)
    {
        // physics collision shapes display?
    }

    float cam_h = (mCamera->GetPosition().y() / mCamera->GetPosition().GetLength());

    if (  cam_h > 0.85 )
    {
        glBlendFunc(GL_ONE, GL_ONE);
        glEnable(GL_BLEND);
    }


    p_it = mProps.find("lamp");

    if (p_it != mProps.end())
    {
        glPushMatrix();
        glTranslatef( (*p_it).second->Position().x(), (*p_it).second->Position().y(),
                     (*p_it).second->Position().z() );
        glRotatef( (*p_it).second->RotationAngle(), (*p_it).second->RotationAxis().x(),
                  (*p_it).second->RotationAxis().y(),
                   (*p_it).second->RotationAxis().z() );
        drawObjModelFixedPipeline( (*p_it).second->ModelPtr(), true );
        glPopMatrix();
    }

    if (  cam_h > 0.7 )
        glDisable(GL_BLEND);
}

void CRenderWindow::drawShadowCastingObjects()
{
    //draw only the objects that will project a shadow

    glColor3ub(255, 255, 255);

    //we only want the pool balls to drop a shadow on the pool table.
    if (!mBalls.empty())
    {
        for (PoolBalls::iterator b_it = mBalls.begin(); b_it != mBalls.end(); ++b_it)
        {
        	drawObjModelFixedPipeline((*b_it).second->ModelPtr(), false);
            //(*b_it).second->BasicDraw(); //draw just the shape, no model, no textures
        }
    }
}

void CRenderWindow::drawReflectedObjects()
{
    SceneProps::iterator p_it = mProps.find("table");

    if (p_it != mProps.end())
    {
        glPushMatrix();
        glTranslatef( (*p_it).second->Position().x(), (*p_it).second->Position().y(),
                     (*p_it).second->Position().z() );
        glRotatef( (*p_it).second->RotationAngle(), (*p_it).second->RotationAxis().x(),
                  (*p_it).second->RotationAxis().y(),
                   (*p_it).second->RotationAxis().z() );
        drawObjModelFixedPipeline( (*p_it).second->ModelPtr(), true );
        glPopMatrix();

    }
}

void CRenderWindow::generateReflectionMap()
{
    if (!useFBO)
        return;

    //render reflection directly to a texture
    glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, mFBO[2]);

    CMatrix4x4 reflProj, reflView;

    //compute and store view and projection matrices relative to the texture viewport
    glPushMatrix();
    glLoadIdentity();
    gluPerspective(45.0f, 1.0, mCamera->GetClipDistanceNear(),
                   mCamera->GetClipDistanceFar());

    glGetFloatv(GL_MODELVIEW_MATRIX, reflProj.Data());

    glLoadIdentity();

    gluLookAt(mCamera->GetPosition().x(), mCamera->GetPosition().y(), mCamera->GetPosition().z(),
              mCamera->GetLookAt().x(), mCamera->GetLookAt().y(), mCamera->GetLookAt().z(),
              mCamera->GetUpVector().x(), mCamera->GetUpVector().y(), mCamera->GetUpVector().z());

    glGetFloatv(GL_MODELVIEW_MATRIX, reflView.Data());

    glPopMatrix();

    //draw color buffer
    GLenum p = GL_COLOR_ATTACHMENT0_EXT;
    glDrawBuffers(1, &p);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //make the clear color transparent, the texture has an alpha component

    glPushAttrib(GL_VIEWPORT_BIT);

    //the viewport is changed to fit the reflection map texture
    glViewport(0,0, reflectionMapWidth, reflectionMapHeight);

    //this call acts on current FBO, not the window!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(reflProj.Data());

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(reflView.Data());

    //clip everything above the ground (there is nothing to clip in this scene,
    //but it's here for safety)
    double plane[4] = {0.0, -1.0, 0.0, 0.0}; //y=0
    glEnable(GL_CLIP_PLANE0);
    glClipPlane(GL_CLIP_PLANE0, plane);

    //flip the objects that will appear in the reflection abuot the reflection plane
    //in this case, the ground.
    glPushMatrix();

    glTranslatef(0.0, 0.0, 0.0);
    glScalef(1.0, -1.0, 1.0);


    //draw lights and the pool table flipped
    if (!mLights.empty())
    {
        list< GLLightPtr >::iterator l_it = mLights.begin();

        GLLightPtr light = (*l_it);

        glLightfv(GL_LIGHT0, GL_POSITION, light->GetPosition());
        glLightfv(GL_LIGHT0, GL_AMBIENT, light->GetAmbientColor());
        glLightfv(GL_LIGHT0, GL_DIFFUSE, light->GetDiffuseColor());
        glLightfv(GL_LIGHT0, GL_SPECULAR, light->GetSpecularColor());
        glEnable(GL_LIGHT0);

        l_it++;

        light = (*l_it);

        glLightfv(GL_LIGHT1, GL_POSITION, light->GetPosition());
        glLightfv(GL_LIGHT1, GL_AMBIENT, light->GetAmbientColor());
        glLightfv(GL_LIGHT1, GL_DIFFUSE, light->GetDiffuseColor());
        glLightfv(GL_LIGHT1, GL_SPECULAR, light->GetSpecularColor());
        glEnable(GL_LIGHT1);

    }

    drawReflectedObjects();

    //return to normal scale and drop the clip plane
    glPopMatrix();

    glDisable(GL_CLIP_PLANE0);

    glPopAttrib();

    //drop the FBO and return to on-screen rendering
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    //the reflection map now needs to be projected so that it will appear
    //correct when bound to the ground texture.
    //projection computation are the same as in shadow mapping
    TexturesMap::iterator t_it = mTextures.find("reflection");

    CMatrix4x4 tmp, reflMatrix;
    tmp = reflView*reflProj*biasMatrix;
    reflMatrix = tmp.Transpose();

    //Note: texture #0 is used to bind all non-projected textures (model textures)
    //texture #1 is used to project all projected textures (shadow and reflection)

    glActiveTextureARB(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, (*t_it).second);

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
    glEnable(GL_TEXTURE_GEN_Q);

    glTexGenfv(GL_S, GL_EYE_PLANE, reflMatrix.GetRow(0));
    glTexGenfv(GL_T, GL_EYE_PLANE, reflMatrix.GetRow(1));
    glTexGenfv(GL_R, GL_EYE_PLANE, reflMatrix.GetRow(2));
    glTexGenfv(GL_Q, GL_EYE_PLANE, reflMatrix.GetRow(3));

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

    //generate mipmaps
    glGenerateMipmapEXT(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE1);
    glDisable(GL_TEXTURE_2D);

}


void CRenderWindow::shadowPassDepth()
{
    if (mLights.empty())
        return;

    TexturesMap::iterator t_it = mTextures.find("shadow");


    //Bind the FBO before clearing the depth buffer!
    if (useFBO)
    {
        //render the depth-pass off screen, in the first frame buffer object
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO[0]);

        //copy the contents of the depth buffer in the shadow map texture. (done automatically)
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                  GL_TEXTURE_2D, (*t_it).second, 0);
    }
    else
        //bind the shadow map texture in preparation for the call to glCopyTexImage2D (no FBO)
        glBindTexture(GL_TEXTURE_2D, (*t_it).second);

    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    //glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

    updateWorldMatrices();

    //this call acts on current FBO, not the window!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //puts the camera in the light position and look towards the origin
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(lightProjection.Data());

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(lightView.Data());

    glPushAttrib(GL_VIEWPORT_BIT);

    //the viewport is changed to fit the shadow map texture
    glViewport(0,0, shadowMapWidth, shadowMapHeight);

    //disable a bunch of opengl options, make the drawing faster and they are not needed.
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_NORMALIZE);
    glDisable(GL_LIGHTING);

    glCullFace(GL_FRONT);
    glShadeModel(GL_FLAT);
    glColorMask(0, 0, 0, 0);

    glEnable(GL_POLYGON_OFFSET_FILL);

    //draw the scene with no ground plane.
    drawShadowCastingObjects();

    glPopAttrib();

    if (useFBO)
        //drop the FBO and return to on-screen rendering
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    else
        //copy the contents of the depth buffer in the shadow map texture. (no FBO)
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, shadowMapWidth,
                         shadowMapHeight, 0);


    //make sure the window buffer is set to normal states

    //reset all openGL options to normal state.
    glCullFace(GL_BACK);
    glShadeModel(GL_SMOOTH);
    glColorMask(1, 1, 1, 1);

    //glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);

    glDisable(GL_POLYGON_OFFSET_FILL);

}

void CRenderWindow::shadowPassDim()
{
    if (mLights.empty())
        return;

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

    //clear color and depth buffer. this time the call acts on the window buffer.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //return the vewpoint to normal camera position and look direction.
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(cameraProjection.Data());

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(cameraView.Data());

    //return the vewport to its original size.
    glViewport(0, 0, mWidth, mHeight);

    //sets the illumination to a dim color
    float dim_color[4] = {0.1f, 0.1f, 0.1f, 1.0f};
    float black[4] = {0.0f, 0.0f, 0.0f, 0.0f};

    //create the light, enable lighting and draw the scene.
    GLLightPtr light = (*mLights.begin());

    glLightfv(GL_LIGHT0, GL_POSITION, light->GetPosition());
    glLightfv(GL_LIGHT0, GL_AMBIENT, dim_color);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, dim_color);
    glLightfv(GL_LIGHT0, GL_SPECULAR, black);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

    drawScene();

}

void CRenderWindow::shadowPassFinal()
{
    if (mLights.empty())
        return;

    //reset the light to its normal intended color
    list<GLLightPtr>::iterator l_it = mLights.begin();

    GLLightPtr light = (*l_it);

    glLightfv(GL_LIGHT0, GL_POSITION, light->GetPosition());
    glLightfv(GL_LIGHT0, GL_AMBIENT, light->GetAmbientColor());
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light->GetDiffuseColor());
    glLightfv(GL_LIGHT0, GL_SPECULAR, light->GetSpecularColor());

    l_it++;

    light = (*l_it);

    glLightfv(GL_LIGHT1, GL_POSITION, light->GetPosition());
    glLightfv(GL_LIGHT1, GL_AMBIENT, light->GetAmbientColor());
    glLightfv(GL_LIGHT1, GL_DIFFUSE, light->GetDiffuseColor());
    glLightfv(GL_LIGHT1, GL_SPECULAR, light->GetSpecularColor());
    glEnable(GL_LIGHT1);

    //compute the shadow projection matrix
    shadowMapProjection();

    //enable alpha testing and texture comparison
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);

    glAlphaFunc(GL_GEQUAL, 0.99f);
    glEnable(GL_ALPHA_TEST);

    //draw the scene one last time
    drawScene();

    //prepare for next pass
    glActiveTexture(GL_TEXTURE1);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);
    glDisable(GL_TEXTURE_GEN_Q);
    glDisable(GL_TEXTURE_2D);

    glActiveTexture(GL_TEXTURE0);
    glDisable(GL_TEXTURE_2D);

    glDisable(GL_ALPHA_TEST);
    glDisable(GL_LIGHTING);

}

void CRenderWindow::standardRender()
{
    //Render the scene without shadows.

    if (useFBO)
        generateReflectionMap();

    //clear buffers
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //make sure all relevant openGL options are properly set (particularly if the render mode
    //has just been switched form shadow-casting back to normal)
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    glViewport(0, 0, mWidth, mHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, double(mWidth)/double(mHeight), mCamera->GetClipDistanceNear(),
                   mCamera->GetClipDistanceFar());

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(mCamera->GetPosition().x(), mCamera->GetPosition().y(), mCamera->GetPosition().z(),
              mCamera->GetLookAt().x(), mCamera->GetLookAt().y(), mCamera->GetLookAt().z(),
              mCamera->GetUpVector().x(), mCamera->GetUpVector().y(), mCamera->GetUpVector().z());

    glEnable(GL_LIGHTING);

    if (!mLights.empty())
    {
        list<GLLightPtr>::iterator l_it = mLights.begin();

        GLLightPtr light = (*l_it);

        glLightfv(GL_LIGHT0, GL_POSITION, light->GetPosition());
        glLightfv(GL_LIGHT0, GL_AMBIENT, light->GetAmbientColor());
        glLightfv(GL_LIGHT0, GL_DIFFUSE, light->GetDiffuseColor());
        glLightfv(GL_LIGHT0, GL_SPECULAR, light->GetSpecularColor());

        l_it++;

        light = (*l_it);

        glLightfv(GL_LIGHT1, GL_POSITION, light->GetPosition());
        glLightfv(GL_LIGHT1, GL_AMBIENT, light->GetAmbientColor());
        glLightfv(GL_LIGHT1, GL_DIFFUSE, light->GetDiffuseColor());
        glLightfv(GL_LIGHT1, GL_SPECULAR, light->GetSpecularColor());
        glEnable(GL_LIGHT1);
    }

    glEnable(GL_NORMALIZE);

    glEnable(GL_TEXTURE_2D);

    drawScene();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
}

void CRenderWindow::selectionRender(int mouseX, int mouseY)
{
    if (endGame)
        return;

    //activate the second FBO, all the picking rendering happens off screen
    if (useFBO)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO[1]);

    //remember to clear the buffers!
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //disable all color-altering options
    glDisable(GL_LIGHTING);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_MULTISAMPLE);
    glShadeModel(GL_FLAT);

    //make sure the look geometry is the same as in the screen render

    //set the curreny viewport
    glViewport(0, 0, mWidth, mHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, double(mWidth)/double(mHeight), mCamera->GetClipDistanceNear(),
    		mCamera->GetClipDistanceFar());


    //set the current modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(mCamera->GetPosition().x(), mCamera->GetPosition().y(), mCamera->GetPosition().z(),
              mCamera->GetLookAt().x(), mCamera->GetLookAt().y(), mCamera->GetLookAt().z(),
              mCamera->GetUpVector().x(), mCamera->GetUpVector().y(), mCamera->GetUpVector().z());


    //draw the balls color-coded. for now, only the cue ball is of interest and is drawn red,
    //whereas all the others are drawn green and their selection is discarded. might be refined.
    glEnable(GL_COLOR_MATERIAL);

    if (!mBalls.empty())
    {
        for (PoolBalls::iterator b_it = mBalls.begin(); b_it != mBalls.end(); ++b_it)
        {
            if ((*b_it).second->Id() == OBJ_CUE_BALL)
                 glColor3f(1.0, 0.0, 0.0);
            else
                 glColor3f(0.0, 1.0, 0.0);

            (*b_it).second->BasicDraw();
            //drawObjModelFixedPipeline((*b_it).second->ModelPtr(), false);
        }
    }

    glDisable(GL_COLOR_MATERIAL);

    //read the pixel under the mouse. opengl coordinates originate in the lower-left corner,
    //screen coordinates in the upper left.
    GLfloat *pick_pixel = new GLfloat[ 3 ];

    glReadBuffer(GL_BACK);
    glReadPixels(mouseX, (mHeight - mouseY), 1, 1, GL_RGB, GL_FLOAT, pick_pixel);

    glFlush();

    glEnable(GL_COLOR_MATERIAL);
    //drop FBO and return to on-screen rendering
    if (useFBO)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    //check the color of the selected pixel and if the cue ball was clicked, change the mouse mode
    //if ( (pick_pixel[0] == 1.0) && (pick_pixel[1] == 0) && (pick_pixel[2] == 0))
    // hack! it doesnt matter where you click!
    if ( (pick_pixel[0] == 0.0) && (pick_pixel[1] == 0) && (pick_pixel[2] == 0))
    {
        SceneProps::iterator it;

        it = mProps.find("cue");

        if ( it != mProps.end() )
        {
            (*it).second->RotationAxis() = CVector3( 0.0, 1.0, 0.0 );
            (*it).second->Heading() = CVector3( 0.0, 0.0, 1.0 );
            CVector3 dir = mCamera->GetLookAt() - mCamera->GetPosition();
            dir.SetComponents(dir.x(), 0.0, dir.z());
            dir = dir * (1.0 / dir.GetLength());
            CVector3 tmp = dir.Cross((*it).second->Heading());

            if ( tmp.y() > 0 )
                (*it).second->RotationAngle() = -acos( dir.Dot((*it).second->Heading()) )*mathc::RAD2DEG;
            else
                (*it).second->RotationAngle() = acos( dir.Dot((*it).second->Heading()) )*mathc::RAD2DEG;

            (*it).second->Heading() = dir;
            (*it).second->Offset() = 0.28;
            (*it).second->Position() = (*mBalls.find(OBJ_CUE_BALL)).second->Position() - dir*(*it).second->Offset();

            cueVelocityAfterHit = (*it).second->Offset() * 5.0;
            cueDirectionAtHit = (*it).second->Heading();

        }


        mousePivot.x = mouseX;
        mousePivot.y = mouseY;
        mWindow->ShowMouseCursor(false);
        shootingMode = true;
    }

    delete pick_pixel;
}

void CRenderWindow::saveScreenshot()
{
	// TODO: handle the path better
	GLubyte *buffer = new GLubyte[3 * width() * height()];

	glReadBuffer(GL_FRONT);
	glReadPixels(0,0, width(), height(), GL_BGR, GL_UNSIGNED_BYTE, buffer);

	FIBITMAP *shot = FreeImage_ConvertFromRawBits(buffer, width(), height(), 3*width(), 24, 0xFF0000, 0x00FF00, 0x0000FF, false);
	FreeImage_Save(FIF_JPEG, shot, "screenshot.jpg");

	delete buffer;
	FreeImage_Unload(shot);
}

void CRenderWindow::shadowRender()
{
    if (useFBO)
        generateReflectionMap();

    shadowPassDepth();
    shadowPassDim();
    shadowPassFinal();
}
